package com.gmrz.uaf.servlet.v1.filter;

import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFBaseProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.google.gson.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Map.Entry;
import java.util.*;

/**
 *@ClassName:RequestAppSecretFilter
 *@Description:TODO  接口访问限制过滤器
 *@date: 2019-10-21 16:18
 **/
public class RequestAppSecretFilter implements Filter {

    private static final Logger LOG = LogManager.getLogger(RequestAppSecretFilter.class);
    private static final String UAF_APPSECRET_VALUE = "uaf.appSecret.value";
    private static final String UAF_APPSECRET_MATCH = "uaf.appSecret.match";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String request = null;
        try{
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            // UAFRequestWrapper：使用其解决request流只能读取一次的问题
            ServletRequest requestWrapper = new UAFRequestWrapper(req);
            request = HttpHelper.getBodyString(requestWrapper);
            LOG.debug("request ："+request);

            JsonObject jsonObject = new JsonParser().parse(request).getAsJsonObject();
            // 判断请求串中国是否存在"context"节点
            if (jsonObject.has("context")){
                JsonObject contextJsonObject = jsonObject.getAsJsonObject("context");
                // 请求中不含有appID，则直接继续执行，例如 查询二维码状态接口的请求串中就不含有appID
                if (contextJsonObject.has("appID")){
                    String appID = contextJsonObject.get("appID").getAsString();
                    // 查询接口访问限制是否开启；先redis中查询，没有再去数据库中查询
                    UAFBaseProcessor uafBaseProcessor = GuiceUtil.getProcessorInjector().getInstance(UAFBaseProcessor.class);
                    String configValue = uafBaseProcessor.getServerConfig().getAppSecretByTenantId(UAF_APPSECRET_MATCH , appID );
                    if (StringUtils.isNotBlank(configValue) && Boolean.parseBoolean(configValue)){
                        // 判断request串中是否存在“license”节点
                        if (jsonObject.has("license")){
                            JsonObject signJsonObject = jsonObject.getAsJsonObject("license");
                            // 获取“sign”节点的value
                            String sign = signJsonObject.get("sign").getAsString();
                            // 查询配置的接口访问限制串
                            configValue = uafBaseProcessor.getServerConfig().getAppSecretByTenantId(UAF_APPSECRET_VALUE , appID );
                            StringBuilder sb = new StringBuilder();
                            sb.append(configValue);
                            sb.append(getContextStr(request));
                            sb.append(configValue);
                            LOG.debug(" appKey - request - appKey： "+sb.toString());
                            // 对使用限制串拼接好的字符串进行MD5 hash
                            String signMd5 = getMD5Digest(sb.toString());

                            if (signMd5 != null && signMd5.equals(sign)){
                                try {
                                    filterChain.doFilter(requestWrapper, servletResponse);
                                }catch (Exception e){
                                    LOG.error("uaf error",e);
                                }
                            }
                            else {
                                getHttpServletResponse(UAFErrorCode.ERROR_APPSECRET_REQUEST_NOT_COMPLETE.getStatusCode(),
                                        UAFErrorCode.ERROR_APPSECRET_REQUEST_NOT_COMPLETE.getMessage(),
                                        servletResponse );
                            }
                        }
                        else {
                            // 请求串中不存在【license】节点
                            getHttpServletResponse(UAFErrorCode.ERROR_APPSECRET_LICENSE_NOT_FOUND.getStatusCode(),
                                    UAFErrorCode.ERROR_APPSECRET_LICENSE_NOT_FOUND.getMessage(),
                                    servletResponse);
                        }
                    }
                    else {
                        // 接口访问限制设置的不开启，则直接继续执行过滤器链
                        try {
                            filterChain.doFilter(requestWrapper, servletResponse);
                        }catch (Exception e){
                            LOG.error("uaf error",e);
                        }
                    }
                }
                else {
                    try {
                        filterChain.doFilter(requestWrapper, servletResponse);
                    }catch (Exception e){
                        LOG.error("uaf error",e);
                    }
                }
            }
            else {
                // 请求串中不存在【context】节点
                getHttpServletResponse(UAFErrorCode.ERROR_APPSECRET_CONTEXT_NOT_FOUND.getStatusCode(),
                        UAFErrorCode.ERROR_APPSECRET_CONTEXT_NOT_FOUND.getMessage(),
                        servletResponse);
            }
        }catch (Exception e){
            LOG.error("uaf error",e);
            LOG.error("request ："+request);
        }
    }

    /**
     *  获得响应
     * @param StatusCode
     * @param Message
     * @param servletResponse
     */
    public static void getHttpServletResponse(int StatusCode, String Message, ServletResponse servletResponse){
        FidoRespPayload respPayload = new FidoRespPayload();
        Gson gson = UAFSchemaBuilder.getGson();
        respPayload.setStatusCode(StatusCode);
        respPayload.setDescription(Message);
        try {
            String response = gson.toJson(respPayload);
            LOG.debug("response：" + response);
            ((HttpServletResponse) servletResponse).getWriter().write(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    /**
     * 获取 json串中的 context 节点所有key的value拼接的字符串
     * @param request
     * @return
     * @throws IOException
     */
    public static String getContextStr(String request) throws IOException{
        JsonObject jsonObject = new JsonParser().parse(request).getAsJsonObject();
        JsonObject contextJsonObject = jsonObject.getAsJsonObject("context");
        String str = parserContext(contextJsonObject);
        return str;
    }

    /**
     *  遍历json串，将key进行排序，然后按排序后的key获取value拼接成字符串
     * @param e
     * @return
     */
    public static String parserContext(JsonElement e)
    {
        List<String> paramNames = new ArrayList<String>();
        Map<String,JsonElement> paramMap = new HashMap<String,JsonElement>();
        StringBuilder result = new StringBuilder();

        Set<Entry<String, JsonElement>> es = e.getAsJsonObject().entrySet();
        // 遍历节点
        for (Entry<String, JsonElement> en : es)
        {
            paramNames.add(en.getKey());
            paramMap.put(en.getKey(), en.getValue());
        }
        // 对节点进行排序
        Collections.sort(paramNames);

        for(String paramName: paramNames){
            String value = null;
            String key = paramName;
            JsonElement jn = paramMap.get(key);
            // 节点的value的类型是object
            if (jn.isJsonObject()) {
                value = parserContext(jn);
            }
            // 节点的value的类型是Array
            else if (jn.isJsonArray()) {
                StringBuilder stringBuilder = new StringBuilder();
                JsonArray ja = jn.getAsJsonArray();
                for (JsonElement ae : ja)
                {
                    stringBuilder.append(ae.getAsString());
                }
                value = stringBuilder.toString();
            }
            // 节点的value的类型是String
            else {
                value = jn.getAsString();
            }
            result.append(value);
        }
        System.out.println("parserContext  key  :  "+Arrays.toString(paramNames.toArray()));
        System.out.println("parserContext  value :  "+result.toString());
        return result.toString();
        //return Arrays.toString(paramNames.toArray());
    }


    /**
     *  将字符串使用“MD5摘要算法”进行hash，得到hash后的字符串，这个就是加密，并且这是无法逆向的，无法解密
     * @param data
     * @return
     */
    public static String getMD5Digest(String data){
        try {
            byte[] bytes = null;
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(data.getBytes("UTF-8"));
            bytes = md.digest();

            StringBuilder stringBuilder = new StringBuilder("");
            for (int i = 0; i < bytes.length; i++) {
                // 0xFF的二进制是 1111 1111 ，然后做与运算时，1 & 1 = 1  ，0 & 1 = 0 ， 0 & 0 =0
                int v = bytes[i] & 0xFF;
                String hv = Integer.toHexString(v);
                if (hv.length() < 2) {
                    stringBuilder.append(0);
                }
                stringBuilder.append(hv);
            }
            return stringBuilder.toString();
        } catch (GeneralSecurityException e) {
            throw new IllegalArgumentException(e);
        }
        catch(UnsupportedEncodingException e){
            throw new IllegalArgumentException(e);
        }
    }


    @Override
    public void destroy() {

    }
}
